use std::fs::{File, OpenOptions};
use std::io::{Read, Seek, SeekFrom, Write};
use std::marker::PhantomData;
use std::path::{Path, PathBuf};

use anyhow::{Context, Result, anyhow, bail};
use bincode::{Decode, Encode};
use serde::Serialize;
use serde::de::DeserializeOwned;

use crate::utils::base32_rfc4648;
use crate::utils::const_assert::{ConstAssert, IsTrue};
use crate::voxel::chunk::Chunk;
use crate::voxel::layout::{chunk_voxel_count, region_chunk_count};

const VERSION_MAJOR: u16 = 1;
const VERSION_MINOR: u16 = 0;

#[inline]
fn bin_config() -> bincode::config::Configuration {
    bincode::config::standard()
}

#[derive(Encode, Decode, Clone, Debug)]
pub struct RegionHeader<const RBITS: u8>
where
    [(); region_chunk_count::<RBITS>()]:,
{
    pub version_major: u16,
    pub version_minor: u16,
    pub chunk_offsets: Box<[u64; region_chunk_count::<RBITS>()]>,
    pub chunk_lengths: Box<[u32; region_chunk_count::<RBITS>()]>,
}

impl<const RBITS: u8> RegionHeader<RBITS>
where
    [(); region_chunk_count::<RBITS>()]:,
{
    pub fn new(version_major: u16, version_minor: u16) -> Self {
        let mut chunk_offsets_vec = Vec::with_capacity(region_chunk_count::<RBITS>());
        chunk_offsets_vec.resize(region_chunk_count::<RBITS>(), 0u64);
        let chunk_offsets: Box<[u64; region_chunk_count::<RBITS>()]> =
            match chunk_offsets_vec.try_into() {
                Ok(b) => b,
                Err(_) => unreachable!(),
            };
        let mut chunk_lengths_vec = Vec::with_capacity(region_chunk_count::<RBITS>());
        chunk_lengths_vec.resize(region_chunk_count::<RBITS>(), 0u32);
        let chunk_lengths: Box<[u32; region_chunk_count::<RBITS>()]> =
            match chunk_lengths_vec.try_into() {
                Ok(b) => b,
                Err(_) => unreachable!(),
            };
        Self {
            version_major,
            version_minor,
            chunk_offsets,
            chunk_lengths,
        }
    }
}

pub struct RegionStore<
    const RBITS: u8,
    const CBITS: u8,
    DATA: Clone + Serialize + DeserializeOwned + Default,
> where
    [(); region_chunk_count::<RBITS>()]:,
    [(); chunk_voxel_count::<CBITS>()]:,
{
    pub header: RegionHeader<RBITS>,
    file: File,
    path: PathBuf,
    _phantom: PhantomData<DATA>,
}

impl<const RBITS: u8, const CBITS: u8, DATA> RegionStore<RBITS, CBITS, DATA>
where
    DATA: Clone + Serialize + DeserializeOwned + Default,
    [(); region_chunk_count::<RBITS>()]:,
    [(); chunk_voxel_count::<CBITS>()]:,
    ConstAssert<{ CBITS <= 8 }>: IsTrue,
    ConstAssert<{ RBITS <= 8 }>: IsTrue,
{
    /// 通过区域坐标打开或创建 Region 文件
    pub fn with_region_pos(x: i32, y: i32, z: i32, base_dir: &Path) -> Result<Self> {
        let file_name = base32_rfc4648::encode_coord(x, y, z);
        Self::open(base_dir.join(file_name))
    }

    pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
        let path_buf = path.as_ref().to_path_buf();
        let mut file = OpenOptions::new()
            .read(true)
            .write(true)
            .create(true)
            .open(&path_buf)
            .with_context(|| format!("open region file {:?}", path_buf))?;

        let file_len = file.metadata()?.len();
        if file_len == 0 {
            let header = RegionHeader::<RBITS>::new(VERSION_MAJOR, VERSION_MINOR);
            bincode::encode_into_std_write(&header, &mut file, bin_config())
                .context("write empty region header")?;
            file.flush()?;
            file.seek(SeekFrom::Start(0))?;
        }

        let header: RegionHeader<RBITS> = bincode::decode_from_std_read(&mut file, bin_config())
            .context("decode region header")?;

        Ok(Self {
            header,
            file,
            path: path_buf,
            _phantom: PhantomData,
        })
    }

    pub fn read_chunk_at(&mut self, index: usize) -> Result<Chunk<CBITS, DATA>> {
        let (&offset, &len) = self
            .header
            .chunk_offsets
            .get(index)
            .zip(self.header.chunk_lengths.get(index))
            .ok_or_else(|| anyhow!("chunk index out of range: {}", index))?;

        if len == 0 {
            bail!("chunk {} is empty", index);
        }

        self.file.seek(SeekFrom::Start(offset))?;
        let mut limited = (&mut self.file).take(len as u64);
        let chunk: Chunk<CBITS, DATA> =
            bincode::serde::decode_from_std_read(&mut limited, bin_config()).with_context(
                || format!("decode chunk at {} (offset={}, len={})", index, offset, len),
            )?;
        Ok(chunk)
    }

    ///
    /// 快速保存Chunk，该函数不会删除旧chunk的数据，只是在文件尾部追加写入。
    /// 如果要清理冗余数据，请调用compact函数。
    ///
    pub fn save(&mut self, chunks: &[(&usize, &Chunk<CBITS, DATA>)]) -> Result<()> {
        // 逐个 chunk 处理
        for (idx_ref, chunk) in chunks {
            let idx = **idx_ref;
            if idx >= self.header.chunk_offsets.len() {
                bail!("chunk index out of range: {}", idx);
            }

            // 先编码成 Vec 拿到长度（serde 路径，便于上层自由定义 DATA）
            let bytes = bincode::serde::encode_to_vec(chunk, bin_config())
                .context("encode chunk (serde)")?;

            // 记录当前文件尾为 offset，写入数据
            let offset = self.file.seek(SeekFrom::End(0))?;
            self.file
                .write_all(&bytes)
                .with_context(|| format!("write chunk {} at offset {}", idx, offset))?;

            // 更新头部索引
            self.header.chunk_offsets[idx] = offset as u64;
            self.header.chunk_lengths[idx] = bytes.len() as u32;
        }

        // 回到文件开头回写头部（Encode 路径）
        self.file.seek(SeekFrom::Start(0))?;
        bincode::encode_into_std_write(&self.header, &mut self.file, bin_config())
            .context("rewrite region header")?;

        self.file.flush()?;
        // self.file.sync_all()?;
        Ok(())
    }

    /// 压缩（重写）Region 文件：仅保留当前版本 chunk，清理历史垃圾
    /// - 写到临时文件
    /// - 原子替换旧文件
    pub fn compact(&mut self) -> Result<()> {
        let tmp_path = self.path.with_extension("tmp");

        // 新文件：清空创建
        let mut newf = OpenOptions::new()
            .read(true)
            .write(true)
            .create(true)
            .truncate(true)
            .open(&tmp_path)
            .with_context(|| format!("create temp file {:?}", tmp_path))?;

        // 预写一个空头（占位）
        let mut new_header =
            RegionHeader::<RBITS>::new(self.header.version_major, self.header.version_minor);
        bincode::encode_into_std_write(&new_header, &mut newf, bin_config())
            .context("write placeholder header to temp")?;
        newf.flush()?;

        // 逐个 chunk 拷贝当前版本
        for i in 0..region_chunk_count::<RBITS>() {
            let len = self.header.chunk_lengths[i] as u64;
            if len == 0 {
                continue;
            }
            let off = self.header.chunk_offsets[i] as u64;

            // 定位源与目标
            self.file.seek(SeekFrom::Start(off))?;
            newf.seek(SeekFrom::End(0))?;
            let new_off = newf.stream_position()?;

            // 拷贝定长字节
            std::io::copy(
                &mut std::io::Read::by_ref(&mut self.file).take(len),
                &mut newf,
            )?;

            // 更新新头的索引
            new_header.chunk_offsets[i] = new_off as u64;
            new_header.chunk_lengths[i] = len as u32;
        }

        // 回写最终头
        newf.seek(SeekFrom::Start(0))?;
        bincode::encode_into_std_write(&new_header, &mut newf, bin_config())
            .context("write final header to temp")?;
        newf.flush()?;
        // newf.sync_all()?; // 需要更强一致性的话打开

        // 原子替换
        drop(newf);
        drop(&self.file);
        std::fs::rename(&tmp_path, &self.path).with_context(|| {
            format!(
                "rename temp {:?} -> {:?} (atomic replace failed)",
                tmp_path, self.path
            )
        })?;

        // 重新打开替换后的文件与头
        let mut reopened = OpenOptions::new().read(true).write(true).open(&self.path)?;
        let header: RegionHeader<RBITS> =
            bincode::decode_from_std_read(&mut reopened, bin_config())?;
        self.file = reopened;
        self.header = header;

        Ok(())
    }
}
